﻿import { CheckoutService } from 'mpj-checkout';
import { DataContextFactory } from './../domain/fbsDataContext';
import { TableParty, Table, TableZone, Cart, Order, Checkout, CartItem, WorkflowOrder, SerinalNoSeed, TableGroup, Store, FrontStoreStock, Inventory } from "../domain/model";
import { ServiceException } from './serviceException';
import { Guid, Logger, ObjectCopier, Tools } from "../common";
import { Transaction, EntityCopier } from "tiny-entity";
import { ICleanService } from '../common/dataClean/cleanManager';
import { TableService } from "./tableService";
import { BaseService } from "./baseService";
import { SyncLock } from '../common/syncLock';

export class OrderService extends BaseService implements ICleanService {
    ctx = DataContextFactory.GetDataContext();
    constructor() {
        super();
    }

    get ServiceName() {
        return "OrderService";
    }

    async CleanData() {
        let ctx = DataContextFactory.GetDataContext();
        // 清理2天前的订单
        let twoDayBefore = this.GetTwoDayBeforeTime();
        let orderIds = await ctx.Order.Where(x => x.status == "closed" || x.status == "cancelled").Where(x => x.createTime < twoDayBefore, ["twoDayBefore"], [twoDayBefore]).Select(x => x.id).ToList();

        let counter = 0;
        for (let item of orderIds) {
            await ctx.Delete(item);
            counter++;
        }

        new Logger().getLogger("app").info("订单数据清理完成：共清除" + counter + "条数据。==============================");
    }

    /**
     * 通过订单id获取订单
     * 
     * @param {string} orderId 订单id
     * @returns 订单信息
     * 
     * @memberOf OrderService
     */
    GetOrderById(orderId: string) {
        return this.ctx.Order.First(x => x.id == orderId, ['orderId'], [orderId]);
    }

    /**
     * 根据台桌组id 获取下面的所有preOrders
     * 
     * @param {string} tableGroupId 
     * @returns {Promise<Order[]>} 
     * 
     * @memberof OrderService
     */
    async GetPreOrderByTableGroupId(tableGroupId: string): Promise<Order[]> {
        let ctx = DataContextFactory.GetDataContext();
        let r = await ctx.TableParty
            .Join<Order>(x => x.master, ctx.Order, "orderId")
            .Where(x => x.tableGroupId == tableGroupId, ["tableGroupId"], [tableGroupId])
            .ToList<{ tableparty: TableParty; orders: Order; }>();

        let result: Order[] = [];
        r.forEach(x => {
            if (x.orders) result.push(x.orders);
        })
        return result;
    }
    /**
     * 获取未结算的订单
     * 
     * 
     * @memberOf OrderService
     */
    async GetUnCheckoutOrderList() {
        let ctx = DataContextFactory.GetDataContext();
        //获取未结算的主订单列表
        let r = await ctx.Order
            .Where(x => x.status == "ordered" || x.status == "presettled")
            .Where(x => x.orderType == "sales")
            .OrderByDesc(x => x.createTime)
            .ToList();
        let result: Order[] = [];
        r.map(x => {
            let access = true;
            let refOrderIdJSONStr = "";
            if (x.refOrderId) {
                refOrderIdJSONStr = JSON.stringify(x.refOrderId);
                if (refOrderIdJSONStr.indexOf("merger") > -1) access = false;
            }
            if (!(x.status == "presettled" && access)) {
                result.push(x);
            }
        });
        return result;
    }

    async GetOrderList(params: { orderType?: string | Array<string>; fromDate?: number; toDate?: number, skip?: number; orderNo?: string, status?: string, checkoutMode?: string }) {
        let ctx = DataContextFactory.GetDataContext();
        let page = params.skip / 20;
        let orderList = ctx.Order.Where(x => x.master == null && x.status != "merged");
        if (params.orderType) {
            if (typeof (params.orderType) == 'string') {
                orderList = orderList.Where(x => x.orderType == params.orderType, ["params.orderType"], [params.orderType]);
            } else if (Array.isArray(params.orderType)) {
                orderList = orderList.Contains(x => x.orderType, params.orderType);
            }
        }
        if (params.fromDate) {
            orderList = orderList.Where(x => x.createTime >= params.fromDate, ["params.fromDate"], [params.fromDate]);
        }
        if (params.toDate) {
            orderList = orderList.Where(x => x.createTime <= params.toDate, ["params.toDate"], [params.toDate]);
        }
        if (params.orderNo) {
            orderList = orderList.Where(x => x.id.IndexOf(params.orderNo), ["params.orderNo"], [params.orderNo]);
        }
        if (params.status) {
            orderList = orderList.Where(x => x.status == params.status, ["params.status"], [params.status]);
        }
        if (params.checkoutMode) {
            orderList = orderList.Where(x => x.checkoutMode == params.checkoutMode, ["params.checkoutMode"], [params.checkoutMode]);
        }
        if (params.skip) {
            orderList = orderList.Skip(page * 20).Take(20);
        }

        let r = await orderList.OrderByDesc(x => x.createTime).ToList();
        return r;
    }

    /**
     * 获取交班相关订单
     * @param openTime 开班时间 
     * @param closeTime 交班时间
     */
    async GetWorkshiftOrders(openTime: number, closeTime: number) {
        return await this.ctx.Order.Where(
            x => x.closeTime >= openTime && x.closeTime <= closeTime,
            ['openTime', 'closeTime'],
            [openTime, closeTime]
        ).Contains(x => x.orderType, ['sales', 'refund', 'macct-deposit']).ToList();
    }

    /**
     * 退单 
     * 
     * @param {Order} order
     * @returns
     * 
     * @memberOf OrderService
     */
    @SyncLock()
    @Transaction(DataContextFactory.GetDataContext())
    async Refund(order: Order) {
        let odId = order.refOrderId[0].id;
        let preOrder = await this.ctx.Order.First(x => x.id == odId, ["odId"], [odId]);
        //更新masterOrder
        let masterOrder = await this.GetOrderById(preOrder.master);

        if (masterOrder.status != "ordered") throw new ServiceException(this, "订单状态错误！");
        if (preOrder.status == "closed") throw new ServiceException(this, "订单状态错误！");
        order.refundCause = Tools.DeleteSpecialCharacter(order.refundCause);
        let refundOrder = new Order();
        refundOrder.cashier = order.cashier
        refundOrder.cashierId = order.cashierId;
        refundOrder.creator = order.creator;
        refundOrder.extended = order.extended;
        refundOrder.storeId = order.storeId;
        refundOrder.terminalName = preOrder.terminalName;

        refundOrder.id = Guid.GetGuid();
        refundOrder.orderNo = refundOrder.id;
        refundOrder.checkoutMode = "post-paid";
        refundOrder.refundCause = order.refundCause ? order.refundCause : "快捷退单";
        refundOrder.status = "closed";
        refundOrder.orderType = "pre-order-refund";
        refundOrder.createTime = new Date().getTime();
        refundOrder.closeTime = new Date().getTime();

        for (let item of order.refOrderId) {
            item.type = "sales";
        }
        refundOrder.refOrderId = order.refOrderId;

        let amountDue = 0;
        for (let item of order.cart.items) {
            //删除pic 
            delete item.inventory.pic;
            if (item.inventory.categories) {
                for (let cItem of item.inventory.categories) {
                    if (cItem.parent) delete cItem.parent.store.owner.pic;
                }
            }

            item.qty = item.refundQty * -1;
            if (item.extended && item.extended.promotion == "present") {
                amountDue = 0;
            }
            else {
                if (item.actualPrice != null && item.actualPrice != undefined) {
                    item.amount = item.qty * item.actualPrice;
                }
                else {
                    item.amount = item.qty * item.price;
                }
                //计算modifiers 中的价格
                if (item.modifiers && item.modifiers.items) {
                    item.amount += item.modifiers.amount * item.qty;
                }
                amountDue += item.amount;
            }


            let cartItem = preOrder.cart.items.find(x => x.id == item.id);
            if (cartItem.status == "refund") throw new ServiceException(this, "状态错误，退菜失败！");
            let masterCartItem = masterOrder.cart.items.find(x => x.id == item.id);

            //item.qty 是负数所以要*-1变成正数
            if (cartItem.refundQty == undefined || cartItem.refundQty == null) {
                cartItem.refundQty = 0;
            }

            cartItem.refundQty += item.refundQty;
            masterCartItem.refundQty = cartItem.refundQty;

            if (cartItem.qty < cartItem.refundQty) {
                throw new ServiceException(this, "退菜错误，数量超出！");
            }

            if (cartItem.qty == cartItem.refundQty) {
                cartItem.status = "refund";
                masterCartItem.status = "refund";
            }

            item.refundQty = 0;
        }

        let floatFlag = 2;
        amountDue = parseFloat(amountDue.toFixed(floatFlag));
        refundOrder.cart = order.cart;
        let inStockParams = [];
        for (let item of refundOrder.cart.items) {
            item.id = Guid.GetGuid();
            inStockParams.push({
                inventoryId: item.inventoryId,
                qty: item.qty
            });
            if (item.extended && item.extended.combo && item.extended.combo.group.length > 0) {
                for (let group of item.extended.combo.group) {
                    if (group.items && group.items.length > 0) {
                        for (let combo of group.items) {
                            inStockParams.push({
                                inventoryId: combo.inventoryId,
                                qty: combo.qty
                            });
                        }
                    }
                }
            }
        }

        if (inStockParams.length > 0) await this.BatchInStock(inStockParams);


        refundOrder.cart.qty = order.cart.items.length;
        refundOrder.amountDue = amountDue;
        refundOrder.remainAmount = amountDue;
        refundOrder.subtotal = amountDue;
        refundOrder.master = preOrder.master;
        refundOrder.serialNo = await this.SerialNoMaker(masterOrder.serialNo);

        //更新PreOrder
        preOrder.refOrderId = [{
            type: "refund",
            id: refundOrder.id
        }];
        preOrder.refundAmount = refundOrder.amountDue;
        preOrder.remainAmount = preOrder.amountDue;

        let l = preOrder.cart.items.filter(x => x.status != "refund");
        if (l.length == 0) {
            preOrder.status = "refund";
        }


        //amountDue 为负数，在masterorder 上扣除退单的金额。
        masterOrder.amountDue += amountDue;
        masterOrder.amountDue = parseFloat(masterOrder.amountDue.toFixed(floatFlag));

        masterOrder.subtotal = CheckoutService.Current.WorkOutOrderSubtotal(<any>masterOrder);

        await this.ctx.Create(refundOrder);
        //更新PreOrder
        await this.ctx.Update(preOrder);
        await this.ctx.Update(masterOrder);

        return {
            refundOrder: refundOrder,
            preOrder: preOrder,
            masterOrder: masterOrder
        };
    }

    async servingOrder() {
        //TODO:servingOrder
        let ctx = DataContextFactory.GetDataContext();
    }

    /**
     * 台桌下单
     * 
     * @param {IOrderingParameter} params 
     * @returns 
     * 
     * @memberof OrderService
     */
    @SyncLock()
    @Transaction(DataContextFactory.GetDataContext())
    async Ordering(params: IOrderingParameter) {
        let terminal = params.Terminal;
        let user = params.User;
        let result: { masterOrder: Order; preOrder: Order; table: Table; tableParty: TableParty; }[] = [];

        let mOrderList = await this.ctx.Order.Contains(x => x.id, params.OrderList.map(n => n.tableParty.orderId)).ToList();
        let serinalNoList = await this.SerialNoMakerCollection(mOrderList.map(x => x.serialNo));

        let OutStockParams = [];
        for (let item of params.OrderList) {
            let masterOrder = mOrderList.find(x => x.id == item.tableParty.orderId);
            if (!masterOrder) throw new ServiceException(this, "台桌缺少主订单！");

            if (item.preOrder.extended && item.preOrder.extended.headCount > 0) {
                masterOrder.extended.headCount = item.preOrder.extended.headCount;
            }

            if (item.preOrder.extended) {
                if (item.preOrder.extended.headCount > 0) masterOrder.extended.headCount = item.preOrder.extended.headCount;
                masterOrder.extended.acctTx = item.preOrder.extended.acctTx ? item.preOrder.extended.acctTx : null;
                masterOrder.extended.mpointsTx = item.preOrder.extended.mpointsTx ? item.preOrder.extended.mpointsTx : null;
            }

            // 由前端传入id
            if (item.preOrder.id != null || item.preOrder.id != undefined) {
                let hasPreOrder = await this.ctx.Order.Any(x => x.id == item.preOrder.id, ["item.preOrder.id"], [item.preOrder.id]);
                if (hasPreOrder) {
                    throw new ServiceException(this, "订单【" + item.preOrder.id + "】已经下单，不能重复下单！");
                }
            }
            else {
                item.preOrder.id = Guid.GetGuid();
            }

            item.preOrder.orderNo = item.preOrder.id;
            item.preOrder.extended || (item.preOrder.extended = {});
            item.preOrder.extended.table = item.table;
            item.preOrder.extended.tableParty = item.tableParty;

            item.preOrder.amountDue = item.preOrder.amountDue ? item.preOrder.amountDue : 0;

            item.preOrder.master = masterOrder.id;
            item.preOrder.extended.tableParty.orderId = masterOrder.id;
            item.preOrder.terminalName = masterOrder.terminalName;
            item.preOrder.serialNo = serinalNoList.find(x => x.origin == masterOrder.serialNo).serialNo;


            item.preOrder = this.GeneratePreOrder(item.preOrder);

            await this.ctx.Create(item.preOrder);

            //更新主订单信息
            masterOrder.status = "ordered";
            masterOrder.extended.tableParty.orderId = masterOrder.id;
            masterOrder.member = item.preOrder.member;
            masterOrder.memberType = item.preOrder.memberType;
            if (item.preOrder.extended.headCount != null && item.preOrder.extended.headCount != undefined) {
                //如果preorder 传入的headcount 则表示想要修改headcount
                masterOrder.extended.headCount = item.preOrder.extended.headCount;
            }
            else {
                item.preOrder.extended.headCount = masterOrder.extended.headCount;
            }

            masterOrder.amountDue += item.preOrder.amountDue;

            if (masterOrder.cart) {
                if (!masterOrder.cart.items) masterOrder.cart.items = [];
                if (masterOrder.cart.qty == null || masterOrder.cart.qty == undefined) masterOrder.cart.qty = 0;
                masterOrder.cart.items = masterOrder.cart.items.concat(item.preOrder.cart.items);
                masterOrder.cart.qty += item.preOrder.cart.qty;
            }
            else {
                masterOrder.cart = {
                    items: item.preOrder.cart.items,
                    qty: item.preOrder.cart.qty
                }
            }

            masterOrder.subtotal = CheckoutService.Current.WorkOutOrderSubtotal(<any>masterOrder);

            // 更新主订单
            await this.ctx.Update(masterOrder);

            //更新tableParty
            item.tableParty.extended || (item.tableParty.extended = <any>{});
            item.tableParty.extended.headCount = masterOrder.extended.headCount;
            item.tableParty.extended.creator = masterOrder.creator;
            //console.timeEnd("Ordering.UpdateMasterOrder");
            result.push({
                table: item.table,
                tableParty: item.tableParty,
                masterOrder: masterOrder,
                preOrder: item.preOrder
            });

            //处理库存扣减
            for (let it of item.preOrder.cart.items) {
                OutStockParams.push({
                    inventoryId: it.inventoryId,
                    qty: it.qty
                });
                if (it.extended && it.extended.combo && it.extended.combo.group.length > 0) {
                    for (let group of it.extended.combo.group) {
                        if (group.items && group.items.length > 0) {
                            for (let combo of group.items) {
                                OutStockParams.push({
                                    inventoryId: combo.inventoryId,
                                    qty: combo.qty
                                });
                            }
                        }
                    }
                }
            }
        }

        let SoldOutInventories = [];
        if (OutStockParams.length > 0) {
            SoldOutInventories = await this.BatchOutStock(OutStockParams);
        }

        return { rst: result, SoldOutInventories: SoldOutInventories };
    }

    private GeneratePreOrder(order: any) {
        let od: Order = new Order();
        let discount: number = order.discount ? order.discount : 0;
        //克隆订单信息 
        od = od.clone(order, od);

        // 订单号
        if (order.cart) {
            order.cart.qty = 0;
            for (let item of order.cart.items) {
                if (od.remainAmount)
                    od.remainAmount += item.amount;
                else od.remainAmount = item.amount;
                order.cart.qty += item.qty;

                if (item.extended && item.extended.description) {
                    item.extended.description = Tools.DeleteSpecialCharacter(item.extended.description);
                }
            }
        }

        if (order.description) {
            order.description = Tools.DeleteSpecialCharacter(order.description);
        }

        od.cart = order.cart;
        od.orderType = "pre-order";
        od.status = "ordered";
        od.subtotal = parseFloat((od.remainAmount - discount).toFixed(3));  // 暂无折扣直接用remainAmount
        od.creatorId = order.creator.id;
        od.creator = order.creator;
        od.createTime = new Date().getTime();
        od.closeTime = 0;
        od.amountDue = parseFloat(od.remainAmount.toFixed(3)); //应付金额
        od.checkoutMode = "post-paid";  //结算模式，“后付费”
        od.extended = order.extended;
        od.description = order.description;
        od.sourceid = order.sourceid;

        delete (<any>od).table;

        return od;
    }

    /**
     * 添加台桌主订单
     * @param  {Order} order
     * @returns Promise
     */
    async CreateMasterOrder(order: Order): Promise<Order> {
        order.id = Guid.GetGuid();
        order.orderNo = order.id;
        order.createTime = new Date().getTime();
        order.closeTime = 0;
        order.status = "init";
        order.orderType = "sales";
        order.checkoutMode = "post-paid";
        order.cart = <any>{
            items: []
        };

        await this.ctx.Create(order);
        return order;
    }
    /**
     * 获取台桌的主订单
     * @param  {string} tableId
     * @returns Promise
     */
    GetMasterOrder(tableId: string, orderStatus: string): Promise<Order> {
        return this.ctx.Order.First(x =>
            x.extended.table.id == tableId && x.status == orderStatus &&
            (x.master == undefined || x.master == undefined) && x.orderType == "sales"
            , ["tableId", "orderStatus"], [tableId, orderStatus]);
    }
    /**
     * 更新台桌主订单
     * @param  {Order} order
     */
    // @Transaction(DataContextFactory.GetDataContext())
    async UpdateMasterOrder(order: Order) {
        // 更新主订单
        let r = await this.ctx.Update(order);

        //更新tableParty
        let tbpId = order.extended.tableParty.id;
        let tbp = await this.ctx.TableParty.First(x => x.id == tbpId, ["tbpId"], [tbpId]);
        tbp.extended || (tbp.extended = <any>{});
        tbp.extended.headCount = order.extended.headCount;
        tbp.extended.creator = order.creator;

        await this.ctx.Update(tbp);
        return r;
    }


    /**
     * 催菜
     * @param  {string} orderId
     * @param  {any} operator
     * @param  {CartItem[]} cartItemList
     */
    async Reminder(orderId: string, operator: { id: string, name: string }, cartItemList: CartItem[]) {
        let ctx = DataContextFactory.GetDataContext();
        let order = await ctx.Order.First(x => x.id == orderId, ["orderId"], [orderId]);

        let preOrderIdList = await ctx.Order.Where(x => x.master == orderId, ["orderId"], [orderId]).Select(x => x.id).ToList();

        if (order.extended.remind) {
            order.extended.remind.remindCount += 1;
            order.extended.remind.operator = operator;
            order.extended.remind.remindTime = new Date();
        }
        else {
            order.extended.remind = {
                remindCount: 1,
                remindTime: new Date(),
                operator: operator
            };
        }

        //更新当前订单信息
        await ctx.Update(order);


        //构造新的 remind-order
        let remindOrder = ObjectCopier.EntityCopy(order, new WorkflowOrder());
        remindOrder.id = Guid.GetGuid();
        remindOrder.orderType = "remind-order";
        remindOrder.extended.lastRemindTime = order.extended.remind.remindTime.getTime();
        let refOrderIds = [];
        preOrderIdList.forEach(x => {
            refOrderIds.push({ id: x.id });
        });
        remindOrder.refOrderId = refOrderIds;

        //去掉pic
        try {
            for (let item of cartItemList) {
                delete item.inventory.pic;
                if (item.inventory) {
                    for (let cItem of item.inventory.categories) {
                        if (cItem.parent) delete cItem.parent.store.owner.pic
                    }
                }
            }
        } catch (error) {
            console.log(error);
        }

        remindOrder.cart.items = cartItemList;

        return remindOrder;
    }

    /**
     * 
     * 
     * @param {string} orderId
     * @param {{ id: string, name: string }} operator
     * @param {CartItem[]} cartItemList
     * @returns
     * 
     * @memberOf OrderService
     */
    async CookingPickUp(orderId: string, operator: { id: string, name: string }, cartItemList: CartItem[]) {
        let ctx = DataContextFactory.GetDataContext();
        let masterOrder = await ctx.Order.First(x => x.id == orderId, ["orderId"], [orderId]);
        //检验重复叫起
        for (let item of cartItemList) {
            let c = masterOrder.cart.items.find(x => x.id == item.id);
            if (c.extended && c.extended.pickup == "pickup") {
                throw new ServiceException(this, "菜品【" + c.inventoryName + "】已被叫起！");
            }
        }

        let preOrderList = await ctx.Order.Where(x => x.master == masterOrder.id, ["masterOrder.id"], [masterOrder.id]).ToList();

        for (let item of cartItemList) {
            let cartItem = masterOrder.cart.items.find(x => x.id == item.id);
            if (cartItem) cartItem.extended.pickup = "pickup";
            for (let preItem of preOrderList) {
                let preCartItem = preItem.cart.items.find(x => x.id == item.id);
                if (preCartItem) preCartItem.extended.pickup = "pickup";
                await ctx.Update(preItem);
            }
        }

        await ctx.Update(masterOrder);

        let pickUpOrder = ObjectCopier.EntityCopy(masterOrder, new WorkflowOrder());
        pickUpOrder.orderType = "pickup-order";
        pickUpOrder.id = Guid.GetGuid();
        pickUpOrder.cart.items = cartItemList;
        pickUpOrder.refOrderId = await ctx.Order.Select(x => x.id).Where(x => x.master == masterOrder.id, ["masterOrder.id"], [masterOrder.id]).ToList();
        return pickUpOrder;
    }

    /**
     * 根据masterOrderId 获取关联的PreOrder列表
     * 
     * @param {string} masterOrderId
     * @returns
     * 
     * @memberOf OrderService
     */
    GetPreOrderByMasterOrder(masterOrderId: string) {
        let ctx = DataContextFactory.GetDataContext();
        return ctx.Order.Where(x => x.master == masterOrderId, ["masterOrderId"], [masterOrderId]).ToList();
    }

    async SerialNoMaker(origin?: any, unSave?: boolean) {
        origin = origin ? origin : "";
        let ctx = DataContextFactory.GetDataContext();
        //origin 如果有域获取的就是子节点 如果没有域就是获取的顶层节点。
        let s = await ctx.SerinalNoSeed.First(x => x.origin == origin, ["origin"], [origin]);
        if (s) {
            let nowDate = new Date().getTime();
            let store = await this.GetStore();

            let lastBusinessTime = new Date();
            lastBusinessTime.setHours(store.setting.businessDuration);
            lastBusinessTime.setMinutes(0);
            lastBusinessTime.setSeconds(0);

            if (s.createTime < lastBusinessTime.getTime() && lastBusinessTime.getTime() < nowDate) {
                s.currentSeed = 1;
                s.createTime = new Date().getTime();
            }
            else {
                s.currentSeed = s.currentSeed + 1;
            }
            s.origin = origin;

            if (!unSave)
                await ctx.Update(s);
        }
        else {
            s = new SerinalNoSeed();
            s.id = Guid.GetGuid();
            s.createTime = new Date().getTime();
            s.currentSeed = 1;
            s.origin = origin;

            if (!unSave)
                await ctx.Create(s);
        }

        if (origin) return s.origin + "-" + s.currentSeed;
        else return s.currentSeed.toString();
    }

    async SerialNoMakerCollection(originList: string[]) {
        let ctx = DataContextFactory.GetDataContext();
        let sList = await ctx.SerinalNoSeed.Contains(x => x.origin, originList).ToList();
        let result: { origin: string; serialNo: string; }[] = [];
        let store = await this.GetStore();
        for (let originId of originList) {
            let s = sList.find(x => x.origin == <any>originId);
            if (s) {
                let nowDate = new Date().getTime();

                let lastBusinessTime = new Date();
                lastBusinessTime.setHours(store.setting.businessDuration);
                lastBusinessTime.setMinutes(0);
                lastBusinessTime.setSeconds(0);

                if (s.createTime < lastBusinessTime.getTime() && lastBusinessTime.getTime() < nowDate) {
                    s.currentSeed = 1;
                    s.createTime = new Date().getTime();
                }
                else {
                    s.currentSeed = s.currentSeed + 1;
                }

                await ctx.Update(s);
            }
            else {
                s = new SerinalNoSeed();
                s.id = Guid.GetGuid();
                s.createTime = new Date().getTime();
                s.currentSeed = 1;
                s.origin = <any>originId;
                await ctx.Create(s);
            }

            result.push({
                origin: s.origin.toString(),
                serialNo: s.origin + "-" + s.currentSeed
            });
        }

        return result;
    }

    /**
     * 回收流水号
     * 
     * @param {number} currentSeed
     * @param {number} [origin]
     * 
     * @memberOf OrderService
     */
    async RecycleSerialNo(currentSeed: number, origin?: number) {
        let ctx = DataContextFactory.GetDataContext();
        let originStr = origin ? origin : "";

        let obj = await ctx.SerinalNoSeed.First(x => x.currentSeed == currentSeed && x.origin == originStr, ["currentSeed", "originStr"], [currentSeed, originStr]);
        if (obj) {
            if (obj.currentSeed == 1) {
                await ctx.Delete(obj);
            }
            else {
                obj.currentSeed -= 1;
                await ctx.Update(obj);
            }
        }
    }



    @Transaction(DataContextFactory.GetDataContext())
    async BatchCheckout(item: any[], user: { id: string, name: string }, terminal) {
        let tableIds = [];
        let tablePartyIds: string[] = [];
        let tableSvr = new TableService();
        for (let i of item) {
            let tableId = await this.Checkout(i, user);
            if ((i.obj.status == "closed" || i.obj.status == "cancelled" || i.obj.status == "merged") && tableId) {
                await tableSvr.UnlockTable(tableId);
                let r = await tableSvr.CloseTable(tableId, terminal.id, true, i.obj.status);
                tablePartyIds.push((<any>(r.table)).tableParty.id);
                tableIds.push(tableId);
            }
        }
        return { tablePartyIds: tablePartyIds, tableIds: tableIds };
    }


    /**
     * 临时的结算方案 在fbs 完成结算服务时删除
     * 
     * 
     * @memberOf OrderService
     */
    // @Transaction(DataContextFactory.GetDataContext())
    private async Checkout(item: any, user: { id: string; name: string }) {
        let orderId = item.obj.id;
        let masterOrder = await this.ctx.Order.First(x => x.id == orderId, ["orderId"], [orderId]);

        if (!item.obj.extended.tableParty) throw new ServiceException(this, "台桌服务数据错误");
        let result = null;

        if (masterOrder) {
            if (masterOrder.status == "closed" || masterOrder.status == "merged" || masterOrder.status == "cancelled") {
                return masterOrder.extended.table.id;
            }
            masterOrder.amountDue = item.obj.amountDue;
            masterOrder.cashier = user;
            masterOrder.cashierId = user.id;
            masterOrder.cart = item.obj.cart;
            masterOrder.checkout = item.obj.checkout;
            masterOrder.checkoutMode = item.obj.checkoutMode;
            masterOrder.closeTime = item.obj.closeTime;
            masterOrder.member = item.obj.member;
            masterOrder.memberType = item.obj.memberType;
            masterOrder.discount = item.obj.discount;
            masterOrder.discounts = item.obj.discounts;
            masterOrder.lastPrintTime = item.obj.lastPrintTime;
            masterOrder.orderDiscount = item.obj.orderDiscount;
            masterOrder.paidAmount = item.obj.paidAmount;
            masterOrder.payments = item.obj.payments;
            masterOrder.printCount = item.obj.printCount;
            masterOrder.refundAmount = item.obj.refundAmount;
            masterOrder.refundCause = item.obj.refundCause;
            masterOrder.refOrderId = item.obj.refOrderId;
            masterOrder.remainAmount = item.obj.remainAmount;
            masterOrder.smallChangeOff = item.obj.smallChangeOff;
            //masterOrder.status = "closed";
            masterOrder.status = item.obj.status;
            masterOrder.subtotal = item.obj.subtotal;
            masterOrder.terminalName = item.obj.terminalName;
            masterOrder.total = item.obj.total;
            masterOrder.processStatus = item.obj.processStatus;
            masterOrder.extended = item.obj.extended;

            await this.ctx.Update(masterOrder);
        }
        else {
            masterOrder = new Order();
            masterOrder = masterOrder.clone(item.obj, masterOrder);
            await this.ctx.Create(masterOrder);
        }

        if (item.obj.status == "closed" || item.obj.status == "cancelled" || item.obj.status == "merged" || item.obj.status == "presettled") {
            let tableSvr = new TableService();

            let tableId = masterOrder.extended.table.id;
            let table = await this.ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
            let tableParty = await tableSvr.GetCurrentTableParty(table.id);
            if (tableParty.id == item.obj.extended.tableParty.id) {
                if (table.status == "cleaning") { delete table.status; }
                else { table.status = ""; }
                table.lock = "";
                delete (<any>table).tableParty;
                await this.ctx.Update(table);
                result = table.id;
            }

            let tpId = masterOrder.extended.tableParty.id;
            let tp = await this.ctx.TableParty.First(x => x.id == tpId, ["tpId"], [tpId]);
            tp.closedTime = new Date().getTime();
            if (tp.status == "cleaning") { delete tp.status; }
            else { tp.status = "closed"; }
            await this.ctx.Update(tp);

            if (item.obj.extended.tableParty.orderId) {
                let preOrderList = await this.GetPreOrderByMasterOrder(item.obj.extended.tableParty.orderId);
                for (let preOrder of preOrderList) {
                    preOrder.status = "closed";
                    preOrder.closeTime = new Date().getTime();
                    await this.ctx.Update(preOrder);
                }
            }
        }


        /********
         * 
         * #### 临时代码
         * 可以在mPOS优化
         * 成功处理合并订单预结算，再结算业务之后
         * 删除以下代码
         * 
         * *****/

        let refOrderId = masterOrder.refOrderId;
        if (refOrderId && refOrderId instanceof Array) {
            let mergerIndex = refOrderId.findIndex(x => x.relation == "merger");
            if (mergerIndex > -1) {
                let mergedItem = refOrderId.findIndex(x => x.relation == "merged-item");
                if (mergedItem > -1) {
                    new Logger().getLogger("error").info("异常关联订单：%s,主订单：%s ", refOrderId[mergerIndex], masterOrder.id);
                    let deleteOrder = new Order();
                    deleteOrder.id = refOrderId[mergerIndex].id;
                    await this.ctx.Delete(deleteOrder);
                }
            }
        }


        return result;

    }

    /**
     * 临时的结算方案 在fbs 完成结算服务时删除
     * 中餐单项赠品
     * 
     * @param {string} masterOrderId
     * @param {CartItem} cartItem
     * 
     * @memberOf OrderService
     */
    async SingleDiscount(preOrder: Order) {
        let ctx = DataContextFactory.GetDataContext();
        let masterOrder = await ctx.Order.First(x => x.id == preOrder.master, ["preOrder.master"], [preOrder.master]);
        if (masterOrder.status == "presettled") {
            throw new ServiceException(this, "该台桌正在结算，无法赠送！");
        }
        let currentTable = await ctx.Table.First(x => x.id == masterOrder.extended.table.id, ["masterOrder.extended.table.id"], [masterOrder.extended.table.id]);
        if (currentTable.lock) {
            throw new ServiceException(this, this.GetLockErrText(currentTable));
        }
        let preOrders = await ctx.Order.Where(x => x.master == masterOrder.id, ["masterOrder.id"], [masterOrder.id]).ToList();
        masterOrder.cart.items = [];
        for (let pO of preOrders) {
            // 如果preOrder列表等于需要更新的...
            if (pO.id == preOrder.id) {
                pO.amountDue = preOrder.amountDue;
                pO.subtotal = preOrder.subtotal;
                pO.discount = preOrder.discount;
                pO.cart = preOrder.cart;
                await ctx.Update(pO);
            }
            if (pO.orderType == "pre-order")
                for (let item of pO.cart.items) {
                    masterOrder.cart.items.push(item);
                }
        }

        await ctx.Update(masterOrder);
        return masterOrder;
    }

    /**
     * 获取业务今日的开始和结束时间
     * 
     * @returns 
     * 
     * @memberOf OrderService
     */
    async GetBusinessToday() {
        let ctx = DataContextFactory.GetDataContext();
        let store = await ctx.Store.First();

        let currentTime = new Date();
        if (currentTime.getHours() < store.setting.businessDuration) {
            currentTime = new Date(currentTime.getTime() - 24 * 3600 * 1000);
        }
        currentTime.setHours(store.setting.businessDuration);
        currentTime.setSeconds(0);
        currentTime.setMinutes(0);

        let fDate = currentTime.getTime();
        let tDate = fDate + 24 * 3600 * 1000;

        return {
            fromDate: fDate,
            toDate: tDate
        }
    }

    /**
     * 处理反结算订单
     * 
     * @returns 
     * 
     * @memberOf counterOrder
     */
    async counterOrder(order: Order) {
        let ctx = DataContextFactory.GetDataContext();
        let counterOrder = await ctx.Order.First(x => x.id == order.id, ["order.id"], [order.id]);
        counterOrder.status = order.status;
        counterOrder.isCounter = order.isCounter;
        counterOrder.counterRemark = order.counterRemark;
        counterOrder.checkout = order.checkout;
        // counterOrder.payments = payments;

        await ctx.Update(counterOrder);
        return counterOrder;
    }

    private MegerInventorys(param: { inventoryId: string, qty: number }[]) {
        // 合并相同商品编号的商品
        let megerd: { inventoryId: string, qty: number }[] = [];
        for (let p of param) {
            let index = megerd.findIndex(x => x.inventoryId == p.inventoryId);
            if (index < 0) {
                megerd.push(p)
            } else {
                megerd[index].qty += p.qty;
            }
        }
        return megerd;
    }

    /**
     * 增加库存
     * 
     * @private
     * @param {string} inventoryId 
     * @param {number} qty 
     * 
     * @memberof OrderService
     */
    private async BatchInStock(param: { inventoryId: string, qty: number }[]) {
        let SoldOutInventories = [];
        let megerd = this.MegerInventorys(param);
        let result = await this.ctx.Inventory
            .Contains(x => x.id, megerd.map(n => n.inventoryId))
            .Join<FrontStoreStock>(x => x.inventoryId, this.ctx.FrontStoreStock, "id")
            .ToList<{ inventory: Inventory; frontstorestock: FrontStoreStock }>();
        for (let r of result) {
            let qty = megerd.find(x => {
                return x.inventoryId == r.inventory.id;
            }).qty;
            if (qty == null || qty == undefined || Math.abs(qty) == 0) throw new ServiceException(this, "库存增加数量不能0")
            qty = Math.abs(qty);
            if (!qty && qty <= 0) throw new ServiceException(this, "未找到相关商品");
            if (r.frontstorestock) {
                r.frontstorestock.quantity += qty;
                SoldOutInventories.push({
                    id: r.inventory.id,
                    isSoldOut: r.frontstorestock.isSoldOut,
                    soldOutMode: r.frontstorestock.soldOutMode,
                    quantity: r.frontstorestock.quantity
                });
                await this.ctx.Update(r.frontstorestock);
            } else {
                let fronstock = new FrontStoreStock();
                fronstock.id = r.inventory.id;
                fronstock.storeId = r.inventory.store.id;
                fronstock.inventoryId = r.inventory.id;
                fronstock.stockEnabled = false;
                fronstock.quantity = qty;
                fronstock.isSoldOut = false;
                fronstock.soldOutMode = "manual";
                SoldOutInventories.push({
                    id: r.inventory.id,
                    isSoldOut: r.frontstorestock.isSoldOut,
                    soldOutMode: r.frontstorestock.soldOutMode,
                    quantity: r.frontstorestock.quantity
                });
                await this.ctx.Create(fronstock);
            }
        }
        return SoldOutInventories;
    }



    /**
     *  批量扣减库存
     * 
     * @private
     * @param {string} inventoryId 
     * @param {number} qty 
     * 
     * @memberof OrderService
     */
    private async BatchOutStock(param: { inventoryId: string, qty: number }[]) {
        let SoldOutInventories = [];
        let megerd = this.MegerInventorys(param);
        let result = await this.ctx.Inventory
            .Contains(x => x.id, megerd.map(n => n.inventoryId))
            .Join<FrontStoreStock>(x => x.inventoryId, this.ctx.FrontStoreStock, "id")
            .ToList<{ inventory: Inventory; frontstorestock: FrontStoreStock }>();
        for (let r of result) {
            let qty = megerd.find(x => {
                return x.inventoryId == r.inventory.id;
            }).qty;
            if (!qty && qty <= 0) throw new ServiceException(this, "未找到相关商品");
            if (r.frontstorestock) {
                // 判断沽清状态
                if (r.frontstorestock.isSoldOut) {
                    throw new ServiceException(this, "商品：‘" + r.inventory.name + "’ 已经沽清！");
                }

                // 处理手工可销售
                if ((r.frontstorestock.soldOutMode == 'manual' || r.frontstorestock.soldOutMode == null)) {
                    r.frontstorestock.quantity -= qty;
                    SoldOutInventories.push({
                        id: r.inventory.id,
                        isSoldOut: r.frontstorestock.isSoldOut,
                        soldOutMode: r.frontstorestock.soldOutMode,
                        quantity: r.frontstorestock.quantity
                    });
                    await this.ctx.Update(r.frontstorestock);

                    // 处理库存沽清可销售
                } else if (r.frontstorestock.soldOutMode == "stock-out") {

                    // 判断可销售数量
                    if (r.frontstorestock.quantity >= qty) {
                        r.frontstorestock.quantity -= qty;

                        // 刚好卖外设置沽清状态
                        if (r.frontstorestock.quantity <= 0) {
                            r.frontstorestock.isSoldOut = true;
                            r.frontstorestock.soldOutMode = "manual";
                        }
                        SoldOutInventories.push({
                            id: r.inventory.id,
                            isSoldOut: r.frontstorestock.isSoldOut,
                            soldOutMode: r.frontstorestock.soldOutMode,
                            quantity: r.frontstorestock.quantity
                        });
                        await this.ctx.Update(r.frontstorestock);
                    } else {
                        throw new ServiceException(this, "商品：‘" + r.inventory.name + "’ 库存不足，可销售数量：" + r.frontstorestock.quantity);
                    }
                }
            } else {
                let fronstock = new FrontStoreStock();
                fronstock.id = r.inventory.id;
                fronstock.storeId = r.inventory.store.id;
                fronstock.inventoryId = r.inventory.id;
                fronstock.stockEnabled = false;
                fronstock.quantity = qty * -1;
                fronstock.isSoldOut = false;
                fronstock.soldOutMode = "manual";
                SoldOutInventories.push({
                    id: r.inventory.id,
                    isSoldOut: fronstock.isSoldOut,
                    soldOutMode: fronstock.soldOutMode,
                    quantity: fronstock.quantity
                });
                await this.ctx.Create(fronstock);
            }

        }
        return SoldOutInventories;
    }

}

export interface IOrderingParameter {
    OrderList: {
        table: Table;
        preOrder: Order;
        tableParty: TableParty;
    }[];
    Terminal: {
        id: string;
        name: string;
    };
    User: {
        id: string;
        name: string;
    }
}